#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>

std::vector<cv::Point2f> control_points;

void mouse_handler(int event, int x, int y, int flags, void *userdata) 
{
    if (event == cv::EVENT_LBUTTONDOWN && control_points.size() < 4) 
    {
        std::cout << "Left button of the mouse is clicked - position (" << x << ", "
        << y << ")" << '\n';
        control_points.emplace_back(x, y);
    }     
}

void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window) 
{
    auto &p_0 = points[0];
    auto &p_1 = points[1];
    auto &p_2 = points[2];
    auto &p_3 = points[3];

    for (double t = 0.0; t <= 1.0; t += 0.001) 
    {
        auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
                 3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;

        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t)
{
    // TODO: Implement de Casteljau's algorithm
    int n;
    if ((n = control_points.size()) == 1) return control_points.back();
    std::vector<cv::Point2f> points;
    for (int i = 1; i < n; ++i) {
        points.push_back((1 - t) * control_points[i - 1] + t * control_points[i]);
    }

    return recursive_bezier(points, t);

}

void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm.

    constexpr int kMax = 700;
    std::vector<std::vector<std::vector<cv::Point2f>>> grids(kMax, std::vector<std::vector<cv::Point2f>>(kMax));

    int minX = kMax, minY = kMax, maxX = 0, maxY = 0;   //bounding box
    int x, y;

    constexpr double kBegin = 0.0, kEnd = 1.0, kStep = 0.0001;
    for (double t = kBegin; t < kEnd; t += kStep) {
        auto point = recursive_bezier(control_points, t);
//        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
        //绘制绿色曲线
        x = point.x; y = point.y;
        minX = std::min(x, minX); maxX = std::max(x, maxX);
        minY = std::min(y, minY); maxY = std::max(y, maxY);
        grids[x][y].push_back(point);
    }

//    return;
    --minX; --minY;
    ++maxX; ++maxY;

    constexpr int kMaxDir = 5;  //上下左右
    const int dirs[kMaxDir][2] = {{0, 0},
                                  {0, 1},
                                  {1, 0},
                                  {0, -1},
                                  {-1, 0}};
    constexpr double kMaxDis = 3;
    double dis, factor;
    int cnt;

    for (int i = minX; i <= maxX; ++i) {
        for (int j = minY; j <= maxY; ++j) {
            double centerX = i + 0.5, centerY = j + 0.5;
            factor = 0; cnt = 0;
            for (int k = 0; k < kMaxDir; ++k) {
                x = i + dirs[k][0]; y = j + dirs[k][1];
                if (x < 0 || y < 0 || x >= kMax || y >= kMax) continue;
                for (auto &grid : grids[x][y]) {
                    dis = (grid.x - centerX) * (grid.x - centerX) + (grid.y - centerY) * (grid.y - centerY);
                    if (dis < kMaxDis) {
                        factor += 1 - dis / kMaxDis;
                        ++cnt;
                    }
                }
            }
            window.at<cv::Vec3b>(j, i)[1] = 255 * factor / cnt; // 除以cnt进行归一化
        }
    }

}

int main() 
{
    cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
    cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
    cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);

    cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

    int key = -1;
    while (key != 27) 
    {
        for (auto &point : control_points) 
        {
            cv::circle(window, point, 3, {255, 255, 255}, 3);
        }

        if (control_points.size() == 4) 
        {
//            naive_bezier(control_points, window);
            bezier(control_points, window);

            cv::imshow("Bezier Curve", window);
            cv::imwrite("my_bezier_curve.png", window);
            key = cv::waitKey(0);

            return 0;
        }

        cv::imshow("Bezier Curve", window);
        key = cv::waitKey(20);
    }

    return 0;
}
